#include <thread>
#include <chrono>
#include <utility>
#include "Util/util.h"
#include "Util/logc.h"
#include "VideoMixer.h"
#include "encoder/VEncoderManager.h"
#include "source/output/OutputSourceManager.h"

namespace Media {

    VideoMixer::VideoMixer() {
        tracef("create VideoMixer");
        m_isRunning = false;
        m_vCompose = nullptr;
    }

    VideoMixer::~VideoMixer() {
        Stop();
        tracef("~VideoMixer");
    }

    bool VideoMixer::Start() {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        if (m_isRunning) return true;

        //createVideoCompose
        VideoComposeDesc pInfo = {};
        createVideoCompose(&pInfo, &m_vCompose);

        if (m_vCompose == nullptr) {
            errorf("createVideoCompose fail");
            return false;
        }

        VideoDisplayInfo info = {};
        info.displayWidth = m_outConfig.m_width;
        info.displayHeight = m_outConfig.m_height;
        m_vCompose->setDisplayInfo(m_vCompose, info);

        //create video encoder
        VEncoderConfig vconfig;
        vconfig.m_bitRate = m_outConfig.m_bitRate;
        vconfig.m_codec = m_outConfig.m_codec;
        vconfig.m_frameRate = m_outConfig.m_frameRate;
        vconfig.m_width = m_outConfig.m_width;
        vconfig.m_height = m_outConfig.m_height;


        if (!VEncoderManager::Instance()->CreateEncoder(m_encoder, vconfig)) {
            return false;
        }

        m_encoder->AddConsumer([this](const FrameCommon::Frame::Ptr &frame) {
            packetProc(frame);
        });

        if (!m_encoder->Start()) {
            return false;
        }

        m_isRunning = true;

        m_frameProcTask = std::async([this]() {
            frameProc();
        });

        tracef("start inputSource items %d, this %p", m_inItems.size(),
               this);
        //start inputSource
        for (auto [k, v]: m_inItems) {
            v->Start();
        }
        return true;
    }

    bool VideoMixer::Stop() {
        tracef("VideoMixer  Stop");

        if (!m_isRunning) return false;

        m_isRunning = false;
        m_frameProcTask.wait();
        //stop inputSource
        for (auto [k, v]: m_inItems) {
            v->Stop();
        }

        //destroy video encoder
        VEncoderManager::Instance()->ReleaseEncoder(m_encoder);
        m_encoder.reset();

        //destroy videoCompose
        destroyVideoCompose(m_vCompose);
        m_vCompose = nullptr;
        return true;
    }

    bool VideoMixer::AddItem(uint32_t itemId, const VideoScenesInItemConfig &config) {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        if (m_inItems.find(itemId) != m_inItems.end()) return false;
        auto vitem = std::make_shared<VideoScenesInItem>(config);
        m_inItems[itemId] = vitem;
        return true;
    }

    bool VideoMixer::SetItem(uint32_t itemId, const VideoScenesInItemConfig &config) {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        if (m_inItems.find(itemId) == m_inItems.end()) return false;
        auto vitem = std::make_shared<VideoScenesInItem>(config);
        m_inItems[itemId] = vitem;

        return true;
    }

    bool VideoMixer::DelItem(uint32_t itemId) {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        if (m_inItems.find(itemId) == m_inItems.end()) return false;
        m_inItems.erase(itemId);
        return true;
    }

    bool VideoMixer::GetItems(std::map<uint32_t, VideoScenesInItemConfig> &items) {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        for (auto [k, v]: m_inItems) {
            items[k] = v->GetItemConfig();
        }
        return true;
    }

    bool VideoMixer::GetOneInItem(uint32_t itemId, VideoScenesInItemConfig &config) {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        if (m_inItems.find(itemId) == m_inItems.end()) return false;
        config = m_inItems[itemId]->GetItemConfig();
        return true;
    }

    bool VideoMixer::SetOutConfig(const VideoScenesOutItemConfig &config) {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        m_outConfig = config;
        return true;
    }

    bool VideoMixer::GetOutConfig(VideoScenesOutItemConfig &config) {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        config = m_outConfig;
        return true;
    }

    bool VideoMixer::AddOutSource(OutputSource::OutputSourceId id, const std::string &name) {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        if (m_outSources.find(id) != m_outSources.end()) return false;
        m_outSources[id] = name;
        return true;
    }

    bool VideoMixer::RemoveOutSource(OutputSource::OutputSourceId id) {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        if (m_outSources.find(id) == m_outSources.end()) return false;
        m_outSources.erase(id);
        return true;
    }


    bool VideoMixer::SetDefaultOutCb(std::function<void(const FrameCommon::Frame::Ptr &)> cb) {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        m_deaultOutCb = std::move(cb);
        return true;
    }

    void VideoMixer::packetProc(const FrameCommon::Frame::Ptr &frame) {
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        for (auto [id, _]: m_outSources) {
            auto source = OutputSourceManager::Instance()->GetSource(id);
            if (source->GetSourceInterfaceEnum() != HDMI) {
                source->InputPacket(frame);
            }
        }

        if (m_deaultOutCb) {
            // tracef("frame pts %llu, size %d, iskey %d", frame->pts(), frame->size(), frame->keyFrame());
            m_deaultOutCb(frame);
        }
    }

    void VideoMixer::frameProc() {
        std::map<uint32_t, AVFrame::Ptr> srcFrame;
        auto last = Fdt::getCurrentMillisecond();
        int frame_duration = 33;

        std::vector<ImageComposeInfo> images;
        while (m_isRunning) {
            images.clear();
            srcFrame.clear();
            {
                std::lock_guard<std::recursive_mutex> guard(m_mux);
//                ImageComposeInfo backIm = {};
//                strcpy(backIm.m_picFilePath, "/nfsroot/defaultImg.yuv");
//                backIm.m_type = LocalPicture;
//                backIm.m_layers = 0;
//                images.push_back(backIm);


//                ImageComposeInfo backIm2 = {};
//                backIm2.m_x = 1000;
//                backIm2.m_type = OSD;
//                backIm2.m_osd.m_color = Red;
//                strcpy(backIm2.m_osd.m_data, "hello world, osd");
//                backIm2.m_osd.m_Size = 48;
//                backIm2.m_width = 1920;
//                backIm2.m_height = 1080;
//                images.push_back(backIm2);

                for (auto [k, v]: m_inItems) {
                    ImageComposeInfo im = {};
                    auto itemConfig = v->GetItemConfig();
                    AVFrame::Ptr frame;
                    if (v->GetFrame(frame)) {
                        im.m_type = General;
                        srcFrame[k] = frame;
                        im.m_inFrame = frame->GetData();
                        im.m_x = itemConfig.m_x;
                        im.m_y = itemConfig.m_y;
                        im.m_width = itemConfig.m_width;
                        im.m_height = itemConfig.m_height;
                        im.m_layers = itemConfig.m_layers;
                        images.push_back(im);
                    } else {
                        // tracef(" display black screen ");
                        // im.m_type = BlackScreen;
                        // im.m_x = itemConfig.m_x;
                        // im.m_y = itemConfig.m_y;
                        // im.m_width = itemConfig.m_width;
                        // im.m_height = itemConfig.m_height;
                        // im.m_layers = itemConfig.m_layers;
                    }
                    // images.push_back(im);
                }

            }

            //images sort
            if (images.empty()) {
                warnf("not found any frame");
                Fdt::sleepMs(frame_duration/3);
                continue;
            }

            void *iFrame = m_vCompose->imageMix(m_vCompose, images.data(), (int)images.size());
            if (iFrame == nullptr) {
                errorf("imageMix fail");
                Fdt::sleepMs(10);
                continue;
            }
            auto now = Fdt::getCurrentMillisecond();
            auto duration = now - last;

            if (duration < frame_duration) {
                Fdt::sleepMs(frame_duration - duration);
            }
            last = Fdt::getCurrentMillisecond();

            //hdmi out
            {
                 std::lock_guard<std::recursive_mutex> guard(m_mux);
                 for(auto [id, _]:m_outSources){
                     auto source = OutputSourceManager::Instance()->GetSource(id);
                     if(source->GetSourceInterfaceEnum() == HDMI){
                         source->InputFrame(iFrame);
                         break;
                     }
                 }
            }

            m_encoder->InputAVFrame(iFrame);
        }
    }

}